﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Threading;

namespace DataIntegrityChecker.Data
{
    class IntegrityChecker
    {
        private List<Proposition> propositions;
        private List<IDataConstraint> constraints;
        private int columnToCheck;
        private DataManager dataManager;
        private double cutoff;
        private bool stop = true;
        private bool isWorking = false;
        public event EventHandler<PropositionAddedEventArgs> RaisePropositionAddedEvent;
        public event EventHandler<PropositionRemovedEventArgs> RaisePropositionRemovedEvent;
        public event EventHandler<PropositionChangedEventArgs> RaisePropositionChangedEvent;
        public event EventHandler<StatusChangedArgs> RaiseStatusChangedEvent;

        public IntegrityChecker(DataManager dataManager, int columnToCheck, double cutoff)
        {
            this.dataManager = dataManager;
            this.columnToCheck = columnToCheck;
            this.cutoff = cutoff;
            this.propositions = new List<Proposition>();
            this.constraints = new List<IDataConstraint>();
        }
        
        internal List<IDataConstraint> Constraints
        {
            get { return constraints; }
            set { constraints = value; }
        }

        public double Cutoff
        {
            get { return cutoff; }
            set { cutoff = value; }
        }

        public int ColumnToCheck
        {
            get { return columnToCheck; }
            set { columnToCheck = value; }
        }

        internal DataManager DataManager
        {
            get { return dataManager; }
            set { dataManager = value; }
        }

        internal List<Proposition> Propositions
        {
            get { return propositions; }
            set { propositions = value; }
        }

        public bool IsWorking
        {
            get { return isWorking; }
            set { isWorking = value; }
        }

        public void Start()
        {
            stop = false; isWorking = true;
            List<int> includedRows = new List<int>();
            int status = 0, total = 0;
            for (int i = 0; i < dataManager.Rows.Count && !stop; i++)
            {
                status = (i + 1); total = dataManager.Rows.Count;
                RaiseStatusChangedEvent(this, new StatusChangedArgs(status, total));
                DataRow iRow = dataManager.Rows[i];
                string iValue = iRow.Values[columnToCheck];
                List<DataRow> equalRows = new List<DataRow>();
                List<DataRow> similarRows = new List<DataRow>();
                for (int j = i; j < dataManager.Rows.Count && !stop; j++)
                {
                    if (!includedRows.Contains(j))
                    {
                        DataRow jRow = dataManager.Rows[j];
                        string jValue = jRow.Values[columnToCheck];
                        double equality = CompareStrings(iValue, jValue);
                        bool constraintsMet = CheckConstraints(iRow, jRow);
                        if (constraintsMet && equality == 1)
                        {
                            equalRows.Add(jRow);
                            includedRows.Add(j);
                        }
                        else if (constraintsMet && equality >= cutoff)
                        {
                            similarRows.Add(jRow);
                            includedRows.Add(j);
                        }
                    }
                }
                if (similarRows.Count > 0)
                {
                    Proposition proposition = new Proposition(iRow, equalRows, similarRows, columnToCheck);
                    proposition.ProposedValue = ProposeValue(proposition);
                    AddProposition(proposition);
                }
            }
            isWorking = false;
            RaiseStatusChangedEvent(this, new StatusChangedArgs(status, total));
        }

        private string ProposeValue(Proposition proposition)
        {
            string result = "";
            List<DataRow> allRows = new List<DataRow>();
            allRows.AddRange(proposition.EqualRows);
            allRows.AddRange(proposition.SimilarRows);
            DataRow bestRow = null;
            int bestCount = -1, count= 0;
            foreach (DataRow row in allRows)
            {
                string value = row.Values[proposition.ColumnToCorrect];
                foreach (DataRow row2 in allRows)
                {
                    if (row2 != row && row2.Values[proposition.ColumnToCorrect].Equals(value))
                        count++;
                }
                if (count > bestCount)
                {
                    bestRow = row; bestCount = count; count = 0;
                }
                else count = 0;
            }
            result = (bestRow != null ? bestRow.Values[proposition.ColumnToCorrect] : "");
            Console.WriteLine(result + "\t" + bestCount);
            return result;
        }

        public void StartAsync()
        {
            Thread thread = new Thread(new ThreadStart(Start));
            thread.IsBackground = false;
            thread.Priority = ThreadPriority.AboveNormal;
            thread.Start();
        }

        public void Stop()
        {
            stop = true;
        }

        public bool CheckConstraints(DataRow row1, DataRow row2)
        {
            bool result = true;
            foreach (IDataConstraint constraint in constraints)
            {
                bool constrResult = constraint.IsConstraintMet(row1, row2);
                result = (constrResult ? result : constrResult);
            }
            return result;
        }

        public void AcceptPropositions()
        {
            foreach (Proposition p in propositions)
            {
                AcceptProposition(p);                
            }
        }

        public void AcceptProposition(Proposition p)
        {
            foreach (DataRow equalRow in p.EqualRows)
            {
                equalRow.Values[p.ColumnToCorrect] = p.ProposedValue;
            }
            while (p.SimilarRows.Count > 0)
            {
                DataRow currentRow = p.SimilarRows[0];
                currentRow.Values[p.ColumnToCorrect] = p.ProposedValue;
                p.EqualRows.Add(currentRow);
                p.SimilarRows.Remove(currentRow);
                EditProposition(p);
            }
        }

        private static double CompareStrings(string str1, string str2)
        {
            if (str1 == null || str1 == "" || str2 == null || str2 == "") return 0.0;

            if (str1.Equals(str2)) return 1.0;

            if (str1.ToLower().Equals(str2.ToLower())) return 0.99;

            if (str1.Replace("\"", "")
                .Replace(",", "")
                .Replace("-", "")
                .ToLower().Equals(
                str2.Replace("\"", "")
                .Replace(",", "")
                .Replace("-", "")
                .ToLower())) return 0.99;


            // Compare on Character level
            char[] chr1 = str1.ToCharArray();
            char[] chr2 = str2.ToCharArray();

            double totalStr1 = str1.Length;
            double totalStr2 = str2.Length;
            double equal1In2 = 0;
            double equal2In1 = 0;
            foreach (char c in chr1)
            {
                if (str2.Contains(c.ToString())) equal1In2++;
            }
            foreach (char c in chr2)
            {
                if (str1.Contains(c.ToString())) equal2In1++;
            }
            return (equal1In2 + equal2In1) / (totalStr1 + totalStr2);
        }

        public void AddProposition(Proposition p)
        {
            propositions.Add(p);
            OnRaisePropositionAddedEvent(new PropositionAddedEventArgs(p));
        }

        public void EditProposition(Proposition p)
        {
            OnRaisePropositionChangedEvent(new PropositionChangedEventArgs(p));
        }

        public void RemoveProposition(Proposition p)
        {
            if(propositions.Remove(p))
                OnRaisePropositionRemovedEvent(new PropositionRemovedEventArgs(p));
        }

        protected virtual void OnRaisePropositionAddedEvent(PropositionAddedEventArgs e)
        {
            EventHandler<PropositionAddedEventArgs> handler = RaisePropositionAddedEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnRaisePropositionRemovedEvent(PropositionRemovedEventArgs e)
        {
            EventHandler<PropositionRemovedEventArgs> handler = RaisePropositionRemovedEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnRaisePropositionChangedEvent(PropositionChangedEventArgs e)
        {
            EventHandler<PropositionChangedEventArgs> handler = RaisePropositionChangedEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnRaiseStatusChangedEvent(StatusChangedArgs e)
        {
            EventHandler<StatusChangedArgs> handler = RaiseStatusChangedEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(this, e);
            }
        }
        
        internal void Clear()
        {
            propositions.Clear();
            OnRaisePropositionRemovedEvent(null);
        }
    }
}
